{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "dbc9463b",
   "metadata": {},
   "source": [
    "## Classifying discrete spacetimes by dimension\n",
    "\n",
    "Note: Breaks on M1 Macs because of `tensorflow` causing kernel panic.\n",
    "\n",
    "[The causal set approach to quantum gravity](https://link.springer.com/article/10.1007/s41114-019-0023-1) postulates spacetime is fundamentally comprised of a set of \"spacetime atoms\", also called elements, together with a set of pairwise causal relations. This approach is deeply rooted in discrete geometry and topology, as well as order theory, and it appeals to practitioners who are minimalists in terms of underlying assumptions of fundamental physics.  While there are many interesting open problems connecting the discrete, quantum world to the continuous, classical realm we are familiar with, in this tutorial we focus on a relatively simple question: is it possible, using machine learning methods, to infer the embedding dimension of a finite causal set spacetime?  If so, what are some of the challenges in accurately measuring such an observable, particularly for small spacetimes on the order of 100 Planck volumes ($10^{-103}m^3$)?\n",
    "\n",
    "We start by installing and importing Covalent, as well as some other relevant libraries:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "2320e9a9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "covalent\n",
      "pandas==1.4.3\n",
      "tensorflow==2.9.1\n",
      "matplotlib==3.4.3\n"
     ]
    }
   ],
   "source": [
    "with open(\"./requirements.txt\", \"r\") as file:\n",
    "    for line in file:\n",
    "        print(line.rstrip())\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "844dfdbd",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install required packages\n",
    "# !pip install -r ./requirements.txt\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "6019ba64-1391-484b-affb-223591eb1831",
   "metadata": {},
   "outputs": [],
   "source": [
    "import covalent as ct\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import tensorflow as tf\n",
    "import matplotlib.pyplot as plt\n",
    "from typing import List, Tuple\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e7f2126b-7cd1-4c85-b86e-47f6e9f84849",
   "metadata": {},
   "source": [
    "## Data Generation\n",
    "\n",
    "In the first part we generate training data for the ML task. Each sample is a sequence of $N$ integers which characterize a single discrete flat spacetime. Samples are generated for either 2D or 3D spacetimes using [Poisson sampling](https://en.wikipedia.org/wiki/Poisson_point_process) in a unit-height Alexandroff interval.  Each coordinate represents an atom of spacetime, also called an element.  Once elements are sampled, we calculate the adjacency matrix of causal relations. A relation \"$\\prec$\" is said to exist between a pair of spacetime elements $(x,y)$ iff they are timelike separated, i.e., if a signal can travel between the points at less than the speed of light.  At this point each spacetime is characterized by $O(N^2)$ degrees of freedom. However, the topology and geometry can be characterized by $O(N)$ degrees of freedom by calculating the size distribution of the \"order intervals\". That is, for each pair of elements $(x,y)$, with $x\\prec y$ count the number of elements $z$ which satisfy $x\\prec z\\prec y$. The distribution of these sizes describes the distribution of neighborhood sizes in Lorentzian spaces."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "2352b209-715b-45e9-8915-0ab9e295fe56",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Generate a single spacetime\n",
    "@ct.electron\n",
    "def generate_flat_spacetime(num_elements: int = 100, dim: int = 2) -> pd.DataFrame:\n",
    "\n",
    "    if dim == 2:\n",
    "        # Sample light cone coordinates in 2D\n",
    "        u = np.random.random(num_elements)\n",
    "        v = np.random.random(num_elements)\n",
    "\n",
    "        # Rotate to Lorentzian coordinates\n",
    "        t = (u + v) / np.sqrt(2)\n",
    "        x = (u - v) / np.sqrt(2)\n",
    "\n",
    "        # Format the coordinates in a dataframe\n",
    "        df = pd.DataFrame(zip(x, t), columns=[\"x\", \"t\"])\n",
    "        df.sort_values(\"t\", inplace=True, ignore_index=True)\n",
    "        return df\n",
    "    elif dim == 3:\n",
    "        # Sample light cone coordinates in 3D\n",
    "        u = np.random.random(num_elements) ** (1.0 / 3)\n",
    "        v = u - np.sqrt(u * u * (1 - np.random.random(num_elements)))\n",
    "        theta = 2 * np.pi * np.random.random(num_elements)\n",
    "\n",
    "        # Coordinate transformation\n",
    "        t = (u + v) / np.sqrt(2)\n",
    "        x = ((u - v) / np.sqrt(2)) * np.cos(theta)\n",
    "        y = ((u - v) / np.sqrt(2)) * np.sin(theta)\n",
    "\n",
    "        # Format the coordinates in a dataframe\n",
    "        df = pd.DataFrame(zip(x, y, t), columns=[\"x\", \"y\", \"t\"])\n",
    "        df.sort_values(\"t\", inplace=True)\n",
    "        return df\n",
    "    else:\n",
    "        raise Exception(f\"Dimension {dim} is not supported!\")\n",
    "\n",
    "\n",
    "# Visualize a single spacetime\n",
    "def visualize_spacetime(coords: pd.DataFrame) -> None:\n",
    "\n",
    "    dim = len(coords.columns)\n",
    "\n",
    "    if dim == 2:\n",
    "        ax = coords.plot.scatter(x=\"x\", y=\"t\")\n",
    "        ax.set_aspect(\"equal\")\n",
    "        ax.set_facecolor(\"white\")\n",
    "    elif dim == 3:\n",
    "        ax = coords.plot.scatter(x=\"x\", y=\"t\")\n",
    "        ax.set_aspect(\"equal\")\n",
    "        ay = coords.plot.scatter(x=\"x\", y=\"y\")\n",
    "        ay.set_aspect(\"equal\")\n",
    "    else:\n",
    "        raise Exception(f\"Dimension {dim} is not supported!\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "65e49e22",
   "metadata": {},
   "source": [
    "Let's pause here to visualize a single discrete spacetime.  We execute the function `generate_flat_spacetime` directly by wrapping it as a lattice and dispatching it. In two dimensions, for instance, we see the region is a diamond shape:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "66501c4e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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Wlpa8N577+HnP/85Vq1ahV/+8pdtMWRlSNiFiIceeggul0v47/PPP3d07NbWVgDAf/zHf2DevHkYPXo0nnnmGVx//fV46aWXOsQ1bN68GR999BGeffbZoI2XRyivw0ptbS1uueUWDBkyBI899ljgAyeUeOqpp/Dmm29i48aN6Nq1a7iHo8zFixdx991344UXXkBKSkq4h+OY1tZW9O7dG3/4wx8wduxY3HHHHXj44Yexdu3acA/Nh07X4qetePDBB/GDH/xAuE1mZib69u2L8+fP+3x++fJlVFZWck1711xzDQBgyJAhPp/fcMMNOHHihPNB2wjlNXz00Uf44osvkJSU5PP5v//7v2Py5MnYunVrACP3JZTXYXLx4kV885vfRM+ePbFx40bExsYGOmwpKSkpiI6Oxrlz53w+P3fuHHe8ffv21dq+LXByHSYrV67EU089hf/7v//DiBEjQjlMKbrX8cUXX6CsrAy33nqr9zNzIRsTE4MjR47guuuuC+2gbTh5Ftdccw1iY2MRHR3t/eyGG27A2bNn0dTUhC5duoR0zMqE22nY2TGDIvbs2eP97O9//7swKKK1tdVIS0vzC1AZNWqUsWzZspCOl4WTazhz5oxx4MABn38AjN/+9rdGaWlpWw3dByfXYRiGUVNTY4wfP974+te/btTX17fFUL3k5OQYixYt8v7d0tJi9OvXTxig8m//9m8+n02YMKFdBKjoXIdhGMavfvUrIyEhwdi5c2dbDFEJneu4dOmS329g1qxZxk033WQcOHDAaGxsbMuhe9F9FsuWLTMGDBhgtLS0eD979tlnjWuuuSbkY9WBhF074Jvf/KYxevRoY/fu3cb27duNrKwsn3D3L7/80rj++uuN3bt3ez975plnjISEBOPtt982iouLjUceecTo2rWrUVJSEo5LcHQNdtBOUg90rqOmpsYYN26cMXz4cKOkpMQ4c+aM99/ly5dDPt4333zTiIuLM1555RXj8OHDxo9+9CMjKSnJOHv2rGEYhnH33XcbDz30kHf7HTt2GDExMcbKlSuNzz77zFi+fHm7ST3QuY6nnnrK6NKli/HnP//Z555fvHgxXJdgGIb+ddhpD9GYutdw4sQJo2fPnsaiRYuMI0eOGH/5y1+M3r17G7/85S/DdQlMSNi1AyoqKow5c+YYPXr0MBISEox58+b5/GiPHTtmADC2bNnis9+KFSuM/v37G/Hx8caECROMTz75pI1HfhWn12ClPQg73evYsmWLAYD579ixY20y5tWrVxvXXnut0aVLFyMnJ8fYtWuX97uvf/3rxty5c322/9Of/mQMHjzY6NKlizF06FDjr3/9a5uMU4bOdQwYMIB5z5cvX972A7eh+zystAdhZxj615Cfn2+MGzfOiIuLMzIzM40nnniiTRZ7OlCLH4IgCCLioWhMgiAIIuIhYUcQBEFEPCTsCIIgiIiHhB1BEAQR8ZCwIwiCICIeEnYEQRBExEPCjiAIgoh4SNgRBEEQEQ8JO4IgCCLiIWFHEARBRDwk7AiCIIiIh4QdQUQQ5eXl6Nu3L5588knvZ/n5+ejSpQs+/PDDMI6MIMILFYImiAjjvffew+zZs5Gfn4/rr78eo0aNwqxZs/D000+He2gEETZI2BFEBLJw4UL83//9H2688UYcOHAAhYWFiIuLC/ewCCJskLAjiAjk0qVLGDZsGE6ePIm9e/di+PDh4R4SQYQV8tkRRATyxRdf4PTp02htbUVZWVm4h0MQYYc0O4KIMJqampCTk4NRo0bh+uuvx7PPPosDBw6gd+/e4R4aQYQNEnYEEWH813/9F/785z/j008/RY8ePfD1r38diYmJ+Mtf/hLuoRFE2CAzJkFEEFu3bsWzzz6L1157DQkJCYiKisJrr72GTz75BM8//3y4h0cQYYM0O4IgCCLiIc2OIAiCiHhI2BEEQRARDwk7giAIIuIhYUcQBEFEPCTsCIIgiIiHhB1BEAQR8ZCwIwiCICIeEnYEQRBExEPCjiAIgoh4SNgRBEEQEQ8JO4IgCCLi+f8BHkRYghEYNIAAAAAASUVORK5CYII=",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "dispatch_id = ct.dispatch(ct.lattice(generate_flat_spacetime))(num_elements=1000, dim=2)\n",
    "coordinates = ct.get_result(dispatch_id, wait=True).result\n",
    "visualize_spacetime(coordinates)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a73db9ca",
   "metadata": {},
   "source": [
    "Now we generate an ensemble of such spacetimes and calculate the causal relations for each:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "ac147ba7-c256-4753-872e-90af7eb17a3f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Generate an ensemble of spacetimes\n",
    "@ct.electron\n",
    "def generate_ensemble(\n",
    "    ensemble_size: int = 1000, num_elements: int = 100, dim: int = 2\n",
    ") -> pd.DataFrame:\n",
    "\n",
    "    return pd.concat(\n",
    "        [\n",
    "            generate_flat_spacetime(num_elements=num_elements, dim=dim)\n",
    "            for x in range(ensemble_size)\n",
    "        ],\n",
    "        ignore_index=True,\n",
    "    )\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "6720e329-24da-4474-95dd-487076783f88",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Identify causal relations between elements\n",
    "@ct.electron\n",
    "def is_related(c1: pd.DataFrame, c2: pd.DataFrame, dim: int) -> bool:\n",
    "\n",
    "    related = False\n",
    "\n",
    "    if dim == 2:\n",
    "        dx = abs(c1[\"x\"] - c2[\"x\"])\n",
    "        dt = abs(c1[\"t\"] - c2[\"t\"])\n",
    "\n",
    "        related = dx < dt\n",
    "    elif dim == 3:\n",
    "        dx = np.sqrt((c1[\"x\"] - c2[\"x\"]) ** 2 + (c1[\"y\"] - c2[\"y\"]) ** 2)\n",
    "        dt = abs(c1[\"t\"] - c2[\"t\"])\n",
    "\n",
    "        related = dx < dt\n",
    "    else:\n",
    "        raise Exception(f\"Dimension {dim} is not supported!\")\n",
    "\n",
    "    return related\n",
    "\n",
    "\n",
    "# Generate adjacency matrices\n",
    "@ct.electron\n",
    "def generate_adj_matrices(ensemble: pd.DataFrame, num_elements: int, dim: int) -> np.ndarray:\n",
    "\n",
    "    num_spacetimes = int(ensemble.shape[0] / num_elements)\n",
    "    adjmat = np.zeros((num_spacetimes, num_elements, num_elements), dtype=bool)\n",
    "\n",
    "    for st in range(num_spacetimes):\n",
    "        coords = ensemble[st * num_elements : (st + 1) * num_elements]\n",
    "        for i, c in coords.iterrows():\n",
    "            for j, d in coords.iterrows():\n",
    "                adjmat[st, i % num_elements, j % num_elements] = is_related(c1=c, c2=d, dim=dim)\n",
    "\n",
    "    return adjmat\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "9256f7d2-42e1-4547-8b0a-b4fe5727c086",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Calculate order intervals\n",
    "# Reduces d.o.f. to O(num_elements)\n",
    "@ct.electron\n",
    "def calculate_intervals(adjmat: np.ndarray) -> pd.DataFrame:\n",
    "\n",
    "    num_spacetimes = len(adjmat)\n",
    "    num_elements = len(adjmat[0, 0])\n",
    "\n",
    "    intervals = np.zeros((num_spacetimes, num_elements), dtype=float)\n",
    "    for st, A in enumerate(adjmat):\n",
    "        intervals[st, 0] = num_elements\n",
    "        for i in range(num_elements):\n",
    "            for j in range(i + 1, num_elements):\n",
    "                count = 0\n",
    "                for k in range(i + 1, j):\n",
    "                    count += int(A[i, k] and A[k, j])\n",
    "                intervals[st, count + 1] += 1\n",
    "\n",
    "    return pd.DataFrame(intervals)\n",
    "\n",
    "\n",
    "# Calculate all intervals across dimensions\n",
    "@ct.electron\n",
    "def calculate_all_intervals(relations: List[np.ndarray]) -> pd.DataFrame:\n",
    "\n",
    "    return pd.concat([calculate_intervals(adjmat=r) for r in relations], ignore_index=True)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "3209d588-a073-42ce-aef0-7e3b4401e69b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Construct training classes\n",
    "@ct.electron\n",
    "def construct_classes(nsamples: int, dimensions: List[int]) -> pd.DataFrame:\n",
    "\n",
    "    classes = pd.DataFrame()\n",
    "\n",
    "    for idx, dim in enumerate(dimensions):\n",
    "        classes[f\"{dim}D\"] = pd.concat(\n",
    "            [\n",
    "                pd.Series([0 for x in range(nsamples * idx)], dtype=float),\n",
    "                pd.Series([1 for x in range(nsamples)], dtype=float),\n",
    "                pd.Series(\n",
    "                    [0 for x in range(nsamples * (idx + 1), nsamples * len(dimensions))],\n",
    "                    dtype=float,\n",
    "                ),\n",
    "            ],\n",
    "            ignore_index=True,\n",
    "        )\n",
    "\n",
    "    return classes\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7b34868",
   "metadata": {},
   "source": [
    "In order to generate the training data, we now combine the above functions into a lattice. There are three computationally heavy tasks which we serialize: the $O(N)$ coordinate sampling, the $O(N^2)$ relations calculation, and the $O(N^3)$ order interval calculation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "44b46ed5-3e71-4d33-b69c-5c11aca1688c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Lattice to generate the data\n",
    "@ct.lattice\n",
    "def generate_training_data(\n",
    "    nsamples, num_elements, dimensions\n",
    ") -> Tuple[pd.DataFrame, pd.DataFrame]:\n",
    "\n",
    "    # Generate the coordinate ensemble\n",
    "    spacetimes = [\n",
    "        generate_ensemble(ensemble_size=nsamples, num_elements=num_elements, dim=d)\n",
    "        for d in dimensions\n",
    "    ]\n",
    "\n",
    "    # Calculate causal relations\n",
    "    relations = [\n",
    "        generate_adj_matrices(ensemble=st, num_elements=num_elements, dim=d)\n",
    "        for st, d in zip(spacetimes, dimensions)\n",
    "    ]\n",
    "\n",
    "    # Calculate order intervals (learning model inputs)\n",
    "    predictors = calculate_all_intervals(relations=relations)\n",
    "\n",
    "    # Construct training classes (learning model outputs)\n",
    "    classes = construct_classes(nsamples=nsamples, dimensions=dimensions)\n",
    "\n",
    "    return predictors, classes\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d27c464b",
   "metadata": {},
   "source": [
    "Let's now visualize the workflow we've constructed so far:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "2ba63303-b50d-4def-992e-aba7a6dea880",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "eee50b6a-2247-47f9-baf7-501746090f89\n"
     ]
    }
   ],
   "source": [
    "# Workflow parameters\n",
    "nsamples = 100\n",
    "size = 20\n",
    "dims = [2, 3]\n",
    "\n",
    "params = {\"nsamples\": nsamples, \"num_elements\": size, \"dimensions\": dims}\n",
    "\n",
    "# Visualize workflow graph on the GUI\n",
    "dispatch_id = ct.dispatch(generate_training_data)(\n",
    "    nsamples=nsamples, num_elements=size, dimensions=dims\n",
    ")\n",
    "print(dispatch_id)\n",
    "\n",
    "# Navigate to localhost:48008 in your browser and examine the latest dispatch entry\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fa9f167b",
   "metadata": {},
   "source": [
    "![Data generation workflow diagram](assets/qg_workflow_1.png)\n",
    "\n",
    "For any of these electrons, represented by nodes in the workflow graph, we can inspect status, query inputs and outputs, debug if there are error messages, and even re-run if a portion failed.  Features like these are useful if, for instance, we had a bug in the code which generated three-dimensional spacetimes.  In this case we could retain the correct two-dimensional data, make required changes to the code, and re-dispatch the lattice to complete the workflow."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2eb2c6f6-716a-4544-9a96-ec49117ca176",
   "metadata": {},
   "source": [
    "## Constructing the Learning Model\n",
    "\n",
    "We continue now by constructing a learning model. We use a five-layer deep neural network trained with supervised learning. The network architecture is chosen such that the size of the layers uniformly decreases with depth, and the learning rate has been selected such that the training procedure converges in a reasonable time.  We also use batch training, i.e., the training operation occurs using randomized batches of 10% of the dataset each epoch, so that the model converges quickly and generalizes well."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "882bfb45-6978-4650-9672-576ca16104cc",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Architecture of the deep neural network\n",
    "\n",
    "# The number of input neurons is equal to the problem size\n",
    "num_inputs = size\n",
    "# The number of outputs is equal to the number of classes\n",
    "num_outputs = len(dims)\n",
    "# We use three hidden layers which decrease in size\n",
    "num_hidden = [int(0.75 * num_inputs), int(0.5 * num_inputs), int(0.25 * num_inputs)]\n",
    "# Total number of layers in the neural network\n",
    "num_layers = 2 + len(num_hidden)\n",
    "# Layer sizes\n",
    "layer_sizes = [num_inputs] + num_hidden + [num_outputs]\n",
    "# Assert the sizes are decreasing\n",
    "assert num_inputs > num_hidden[0]\n",
    "assert num_hidden[-1] > num_outputs\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "a6a0b889-7497-4abb-9088-f8ec831fd270",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Shuffle the data\n",
    "@ct.electron\n",
    "def shuffle_training_data(\n",
    "    predictors: pd.DataFrame, classes: pd.DataFrame\n",
    ") -> Tuple[pd.DataFrame, pd.DataFrame]:\n",
    "\n",
    "    num_inputs = len(predictors.columns)\n",
    "    num_outputs = len(classes.columns)\n",
    "\n",
    "    shuffled_data = pd.concat([predictors, classes], axis=1).sample(frac=1).reset_index(drop=True)\n",
    "\n",
    "    shuffled_predictors = shuffled_data[shuffled_data.columns[:num_inputs]]\n",
    "    shuffled_classes = shuffled_data[shuffled_data.columns[-num_outputs:]]\n",
    "\n",
    "    return shuffled_predictors, shuffled_classes\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "ed191dac-cc9a-44b6-a8dc-0a1554b27df6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Construct the learning model\n",
    "@ct.electron(executor=\"local\")\n",
    "def construct_model(layers: List[int]) -> tf.keras.Model:\n",
    "\n",
    "    # Model architecture described by layers\n",
    "    num_layers = len(layers)\n",
    "    num_inputs = layers[0]\n",
    "    num_outputs = layers[-1]\n",
    "\n",
    "    model = tf.keras.Sequential()\n",
    "    model.add(tf.keras.Input(shape=(num_inputs,)))\n",
    "    for layer in range(num_layers - 1):\n",
    "        model.add(tf.keras.layers.Dense(layers[layer + 1], activation=\"relu\"))\n",
    "\n",
    "    model.compile(\n",
    "        optimizer=\"adam\",\n",
    "        loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),\n",
    "        metrics=[\"accuracy\"],\n",
    "    )\n",
    "\n",
    "    return model\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9bb2384b-0342-4078-9055-5dce9e760496",
   "metadata": {},
   "source": [
    "**Note**: We use the \"local\" executor for this electron because the default Dask-based executor sometimes fails to preserve object attributes when transferring data to and from worker processes. The underlying bug will be addressed in a future release. Should you encounter runtime issues, try setting `executor=\"local\"` for some electrons."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "ff1ad490",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"sequential\"\n",
      "_________________________________________________________________\n",
      " Layer (type)                Output Shape              Param #   \n",
      "=================================================================\n",
      " dense (Dense)               (None, 15)                315       \n",
      "                                                                 \n",
      " dense_1 (Dense)             (None, 10)                160       \n",
      "                                                                 \n",
      " dense_2 (Dense)             (None, 5)                 55        \n",
      "                                                                 \n",
      " dense_3 (Dense)             (None, 2)                 12        \n",
      "                                                                 \n",
      "=================================================================\n",
      "Total params: 542\n",
      "Trainable params: 542\n",
      "Non-trainable params: 0\n",
      "_________________________________________________________________\n",
      "None\n"
     ]
    }
   ],
   "source": [
    "# Summarize the model using synchronous dispatch\n",
    "model = ct.dispatch_sync(ct.lattice(construct_model))(layers=layer_sizes).result\n",
    "print(model.summary())\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c1e2f0e1-20b2-45f5-a993-69693fd50b2e",
   "metadata": {},
   "source": [
    "## Train the model\n",
    "\n",
    "In the next step, we train the model using the data we generated at the beginning.  We use a cross-entropy cost function and measure the accuracy every 100 epochs.  What we find is that learning occurs spontaneously, i.e., there is a jump in accuracy after some characteristic time which depends on the learning rate, system size, and number of training classes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "id": "347595fa-a8f2-43a8-b80e-0052c5e64260",
   "metadata": {},
   "outputs": [],
   "source": [
    "@ct.electron\n",
    "def train_model(\n",
    "    model: tf.keras.Model, predictors: pd.DataFrame, classes: pd.DataFrame, num_training_steps: int\n",
    ") -> tf.keras.callbacks.History:\n",
    "\n",
    "    history = model.fit(predictors, classes, epochs=num_training_steps)\n",
    "\n",
    "    return history\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "a6754321-7bbd-471c-bfb4-228f9efce0fd",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Machine learning workflow\n",
    "@ct.lattice\n",
    "def spacetime_learning_workflow(\n",
    "    arch: List[int],\n",
    "    training_predictors: pd.DataFrame,\n",
    "    training_classes: pd.DataFrame,\n",
    "    num_training_steps: int,\n",
    ") -> Tuple[tf.keras.Model, tf.keras.callbacks.History]:\n",
    "\n",
    "    model = construct_model(layers=arch)\n",
    "\n",
    "    history = train_model(\n",
    "        model=model,\n",
    "        predictors=training_predictors,\n",
    "        classes=training_classes,\n",
    "        num_training_steps=num_training_steps,\n",
    "    )\n",
    "\n",
    "    return model, history\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bdf1a97c",
   "metadata": {},
   "source": [
    "Before jumping to the final step, let's again examine the workflow in the user interface."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "8f9a9c10",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<covalent._results_manager.result.Result at 0x296c37ee0>"
      ]
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "training_predictors, training_classes = generate_training_data(\n",
    "    nsamples=100, num_elements=20, dimensions=dims\n",
    ")\n",
    "\n",
    "ct.dispatch_sync(spacetime_learning_workflow)(\n",
    "    arch=layer_sizes,\n",
    "    training_predictors=training_predictors,\n",
    "    training_classes=training_classes,\n",
    "    num_training_steps=1,\n",
    ")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "461a36a5",
   "metadata": {},
   "source": [
    "![Spacetime learning workflow diagram](./assets/qg_workflow_2.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "516b420d-c009-4c92-9466-749e9cb63d64",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Higher-level workflow\n",
    "@ct.lattice\n",
    "def spacetime_classifier(\n",
    "    nsamples: int, size: int, dims: List[int], num_training_steps: int\n",
    ") -> Tuple[tf.keras.Model, tf.keras.callbacks.History]:\n",
    "\n",
    "    # Call the first sublattice, which generates training data\n",
    "    training_predictors, training_classes = ct.electron(generate_training_data)(\n",
    "        nsamples=nsamples, num_elements=size, dimensions=dims\n",
    "    )\n",
    "\n",
    "    # Shuffle the training data\n",
    "    training_predictors, training_classes = shuffle_training_data(\n",
    "        predictors=training_predictors, classes=training_classes\n",
    "    )\n",
    "\n",
    "    # Call the next sublattice, which constructs and trains a learning model\n",
    "    model, history = ct.electron(spacetime_learning_workflow)(\n",
    "        arch=layer_sizes,\n",
    "        training_predictors=training_predictors,\n",
    "        training_classes=training_classes,\n",
    "        num_training_steps=num_training_steps,\n",
    "    )\n",
    "\n",
    "    return model, history\n",
    "\n",
    "\n",
    "# Dispatch the full workflow\n",
    "model, history = ct.dispatch_sync(spacetime_classifier)(\n",
    "    nsamples=nsamples, size=size, dims=dims, num_training_steps=500\n",
    ").result\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "404040de",
   "metadata": {},
   "source": [
    "Let's inspect the final workflow.  Note that the nodes marked \"sublattice\" represent the two workflows shown in previous diagrams.\n",
    "\n",
    "![Spacetime classifier workflow](./assets/qg_workflow_3.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7442c848",
   "metadata": {},
   "source": [
    "## Results\n",
    "\n",
    "Let's now look at the ability of the model to predict the outcome given new data.  We can first look at the training history to confirm the model has learned to categorize the training set properly.  We then generate a set of new samples and look at the accuracy of the classified results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "id": "2a1217d1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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zjvvdJKSkpy8vDzn008/dc4//3znggsu8O3/6aefnF69ejmZmZnOZ5995rzzzjtObGysM3HiRBuXdEr6+9//7rz99tvOV1995RQVFTmPPPKI06xZM2fjxo2O43CPG8OaNWucTp06OSkpKc7YsWN927nXDWPKlCnOOeec4+zevdv32bNnj2//qXSfCT+G9O/f37n77rt9v1dXVzuJiYlObm6uxV6dvo4NP16v14mPj3d+97vf+baVlZU5brfbWbx4seM4jvPFF184kpxPPvnEd8y7777ruFwu57vvvjPW99NNaWmpI8lZuXKl4zhH7muzZs2cv/71r75jNm/e7EhyCgoKHMc5ElRDQkKc4uJi3zFz5851oqKinKqqKrMXcBpp3bq186c//Yl73Aj279/vdOvWzVm+fLlzySWX+MIP97rhTJkyxenTp89x951q95lhLwMOHTqktWvXKjMz07ctJCREmZmZKigosNizpmP79u0qLi72u8fR0dFKT0/33eOCggLFxMQoLS3Nd0xmZqZCQkK0evVq430+XZSXl0uS2rRpI0lau3atDh8+7Heve/TooQ4dOvjd6969eysuLs53TFZWljwejzZt2mSw96eH6upqLVmyRJWVlcrIyOAeN4K7775bQ4YM8bunEv+eG9qWLVuUmJiozp07a8SIEdqxY4ekU+8+s7CpAXv37lV1dbXff1BJiouL05dffmmpV01LcXGxJB33HtfsKy4uVrt27fz2h4WFqU2bNr5j4M/r9WrcuHG68MIL1atXL0lH7mN4eLhiYmL8jj32Xh/vv0XNPhyxYcMGZWRk6ODBg2rZsqXeeOMN9ezZU4WFhdzjBrRkyRKtW7dOn3zySa19/HtuOOnp6Vq4cKG6d++u3bt3a+rUqbrooou0cePGU+4+E34A1Onuu+/Wxo0b9dFHH9nuSpPUvXt3FRYWqry8XK+99ppGjRqllStX2u5Wk7Jz506NHTtWy5cvV0REhO3uNGmDBw/2/ZySkqL09HR17NhRr776qiIjIy32rDaGvQyIjY1VaGhorVntJSUlio+Pt9SrpqXmPv7SPY6Pj1dpaanf/p9++knff/89/x2O45577tFbb72lDz74QGeeeaZve3x8vA4dOqSysjK/44+918f7b1GzD0eEh4era9eu6tevn3Jzc9WnTx/9/ve/5x43oLVr16q0tFR9+/ZVWFiYwsLCtHLlSj333HMKCwtTXFwc97qRxMTE6KyzztLWrVtPuX/ThB8DwsPD1a9fP+Xl5fm2eb1e5eXlKSMjw2LPmo7k5GTFx8f73WOPx6PVq1f77nFGRobKysq0du1a3zErVqyQ1+tVenq68T6fqhzH0T333KM33nhDK1asUHJyst/+fv36qVmzZn73uqioSDt27PC71xs2bPALm8uXL1dUVJR69uxp5kJOQ16vV1VVVdzjBjRw4EBt2LBBhYWFvk9aWppGjBjh+5l73TgqKiq0bds2JSQknHr/pht0+jTqtGTJEsftdjsLFy50vvjiC+eOO+5wYmJi/Ga145ft37/f+eyzz5zPPvvMkeQ8++yzzmeffeZ88803juMcedQ9JibG+dvf/uasX7/eufbaa4/7qPu5557rrF692vnoo4+cbt268aj7Me666y4nOjrayc/P93tk9cCBA75j7rzzTqdDhw7OihUrnE8//dTJyMhwMjIyfPtrHlm98sorncLCQmfZsmVO27ZteTT4Zx5++GFn5cqVzvbt253169c7Dz/8sONyuZz33nvPcRzucWP6+dNejsO9bijjx4938vPzne3btzurVq1yMjMzndjYWKe0tNRxnFPrPhN+DHr++eedDh06OOHh4U7//v2djz/+2HaXTisffPCBI6nWZ9SoUY7jHHncfdKkSU5cXJzjdrudgQMHOkVFRX5t7Nu3z7n55pudli1bOlFRUU52drazf/9+C1dz6jrePZbkvPTSS75jfvzxR2fMmDFO69atnebNmzvXXXeds3v3br92vv76a2fw4MFOZGSkExsb64wfP945fPiw4as5dd12221Ox44dnfDwcKdt27bOwIEDfcHHcbjHjenY8MO9bhjDhg1zEhISnPDwcKd9+/bOsGHDnK1bt/r2n0r32eU4jtOwtSQAAIBTF3N+AABAUCH8AACAoEL4AQAAQYXwAwAAggrhBwAABBXCDwAACCqEHwAAEFQIPwAAIKgQfgCgHvLz8+VyuWotzAjg9EP4AQAAQYXwAwAAggrhB8Bpwev1Kjc3V8nJyYqMjFSfPn302muvSTo6JPX2228rJSVFEREROv/887Vx40a/Nv73f/9X55xzjtxutzp16qRnnnnGb39VVZUeeughJSUlye12q2vXrnrxxRf9jlm7dq3S0tLUvHlzXXDBBSoqKmrcCwfQ4Ag/AE4Lubm5WrRokebNm6dNmzbp/vvv13//939r5cqVvmMefPBBPfPMM/rkk0/Utm1bXX311Tp8+LCkI6Hlpptu0vDhw7VhwwY9/vjjmjRpkhYuXOg7f+TIkVq8eLGee+45bd68WS+88IJatmzp149HH31UzzzzjD799FOFhYXptttuM3L9ABoOq7oDOOVVVVWpTZs2ev/995WRkeHb/tvf/lYHDhzQHXfcocsuu0xLlizRsGHDJEnff/+9zjzzTC1cuFA33XSTRowYoT179ui9997znT9hwgS9/fbb2rRpk7766it1795dy5cvV2ZmZq0+5Ofn67LLLtP777+vgQMHSpLeeecdDRkyRD/++KMiIiIa+S4AaChUfgCc8rZu3aoDBw7oiiuuUMuWLX2fRYsWadu2bb7jfh6M2rRpo+7du2vz5s2SpM2bN+vCCy/0a/fCCy/Uli1bVF1drcLCQoWGhuqSSy75xb6kpKT4fk5ISJAklZaWnvQ1AjAnzHYHAODXVFRUSJLefvtttW/f3m+f2+32C0CBioyMrNdxzZo18/3scrkkHZmPBOD0QeUHwCmvZ8+ecrvd2rFjh7p27er3SUpK8h338ccf+37+4Ycf9NVXX+nss8+WJJ199tlatWqVX7urVq3SWWedpdDQUPXu3Vter9dvDhGAponKD4BTXqtWrfTAAw/o/vvvl9fr1YABA1ReXq5Vq1YpKipKHTt2lCRNmzZNZ5xxhuLi4vToo48qNjZWQ4cOlSSNHz9e5513nqZPn65hw4apoKBAs2fP1h/+8AdJUqdOnTRq1Cjddttteu6559SnTx998803Ki0t1U033WTr0gE0AsIPgNPC9OnT1bZtW+Xm5urf//63YmJi1LdvXz3yyCO+YacZM2Zo7Nix2rJli1JTU/WPf/xD4eHhkqS+ffvq1Vdf1eTJkzV9+nQlJCRo2rRpuvXWW31/Y+7cuXrkkUc0ZswY7du3Tx06dNAjjzxi43IBNCKe9gJw2qt5EuuHH35QTEyM7e4AOMUx5wcAAAQVwg8AAAgqDHsBAICgQuUHAAAEFcIPAAAIKoQfAAAQVAg/AAAgqBB+AABAUCH8AACAoEL4AQAAQYXwAwAAgsr/B6Awp2yzLbuoAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(history.history[\"accuracy\"])\n",
    "plt.xlabel(\"epoch\")\n",
    "plt.ylabel(\"accuracy\")\n",
    "plt.rcParams[\"figure.facecolor\"] = \"white\"\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "16778465",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:tensorflow:Assets written to: ram://b680e782-8666-48ca-ba48-4be81fc4ea90/assets\n",
      "INFO:tensorflow:Assets written to: ram://0a5b37c4-3e5e-4156-a1e5-8955dc846301/assets\n",
      "7/7 [==============================] - 0s 9ms/step - loss: 2.8095 - accuracy: 0.1100\n",
      "Model Accuracy: 0.10999999940395355\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2022-09-08 00:18:09.003971: I tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.cc:113] Plugin optimizer for device_type GPU is enabled.\n"
     ]
    }
   ],
   "source": [
    "@ct.electron\n",
    "def evaluate_model(\n",
    "    model: tf.keras.Model, predictors: pd.DataFrame, classes: pd.DataFrame\n",
    ") -> float:\n",
    "\n",
    "    cost, accuracy = model.evaluate(predictors, classes)\n",
    "\n",
    "    return accuracy\n",
    "\n",
    "\n",
    "# Generate test data and test the model\n",
    "@ct.lattice\n",
    "def test_model(\n",
    "    model: tf.keras.Model, nsamples: int, num_elements: int, dimensions: List[int]\n",
    ") -> float:\n",
    "\n",
    "    testing_predictors, testing_classes = ct.electron(generate_training_data)(\n",
    "        nsamples=nsamples, num_elements=num_elements, dimensions=dimensions\n",
    "    )\n",
    "\n",
    "    testing_predictors, testing_classes = shuffle_training_data(\n",
    "        predictors=testing_predictors, classes=testing_classes\n",
    "    )\n",
    "\n",
    "    test_accuracy = evaluate_model(\n",
    "        model=model, predictors=testing_predictors, classes=testing_classes\n",
    "    )\n",
    "\n",
    "    return test_accuracy\n",
    "\n",
    "\n",
    "accuracy = ct.dispatch_sync(test_model)(\n",
    "    model=model, nsamples=nsamples, num_elements=size, dimensions=dims\n",
    ").result\n",
    "accuracy = test_model(model=model, nsamples=nsamples, num_elements=size, dimensions=dims)\n",
    "print(f\"Model Accuracy: {accuracy}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "76829e96",
   "metadata": {},
   "source": [
    "## Summary\n",
    "\n",
    "In this tutorial we used Covalent in a variety of ways. In several sections we constructed workflows which we later combined into a larger workflow by making them sublattices.  Using the Covalent UI, we were able to visualize and track the progress of the experiments. An advanced user could even continue along the same path to examine how the number of epochs needed to learn to classify can change with the problem size."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "mnist",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.13 (default, Oct 19 2022, 17:54:22) \n[Clang 12.0.0 ]"
  },
  "vscode": {
   "interpreter": {
    "hash": "09f31237d8087fb717019708595cb8a07fe1ba4ed422094db14ddfd267d3815a"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
